package com.example.activiti7.domain.service.impl;

import com.example.activiti7.domain.model.req.ActBizModelReq;
import com.example.activiti7.domain.model.req.DeployReq;
import com.example.activiti7.domain.model.req.QueryProcessDefinitionListReq;
import com.example.activiti7.domain.model.resp.Page;
import com.example.activiti7.domain.model.resp.Response;
import com.example.activiti7.domain.model.vo.ProcessDefinitionVO;
import com.example.activiti7.domain.service.ProcessDefinitionService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.util.IoUtil;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service
public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ObjectMapper objectMapper;

    @Override
    public Response<Model> create(ActBizModelReq actModelReq) throws Exception {
        if (StringUtils.isBlank(actModelReq.getName())) {
            return Response.error("流程模型创建失败,名称不能为空");
        }
        if (StringUtils.isBlank(actModelReq.getDescription())) {
            return Response.error("流程模型创建失败,描述不能为空");
        }
        Model model = repositoryService.newModel();
        saveActModel(model, actModelReq);
        createSaveDefaultObjectNode(model);

        return Response.success(model, "流程模型创建成功!");
    }

    @Override
    public ObjectNode getModelEditorJson(String modelId) throws Exception {
        ObjectNode modelNode = null;
        Model model = repositoryService.getModel(modelId);
        if (model != null) {
            if (StringUtils.isNotEmpty(model.getMetaInfo())) {
                modelNode = (ObjectNode) this.objectMapper.readTree(model.getMetaInfo());
            } else {
                modelNode = this.objectMapper.createObjectNode();
                modelNode.put("name", model.getName());
            }

            modelNode.put("modelId", model.getId());
            ObjectNode editorJsonNode = (ObjectNode) this.objectMapper.readTree(new String(repositoryService
                    .getModelEditorSource(model
                            .getId()), "utf-8"));
            modelNode.set("model", editorJsonNode);

        }
        return modelNode;
    }

    @Override
    public boolean save(String modelId, MultiValueMap<String, String> values) throws Exception {
        if (StringUtils.isEmpty(values.getFirst("json_xml"))) {
            throw new Exception("数据格式错误");
        }

        ActBizModelReq actBizModelReq = buildActBizModelReq(modelId, values);
        Model model = this.repositoryService.getModel(actBizModelReq.getModelId());
        //保存model信息
        saveActModel(model, actBizModelReq);
        // 保存模型设计编辑信息
        this.repositoryService.addModelEditorSource(modelId, values.getFirst("json_xml").getBytes("utf-8"));
        //保存模型设计编辑扩展信息
        InputStream svgStream = null;
        ByteArrayOutputStream outStream = null;
        try {
            svgStream = new ByteArrayInputStream(values.getFirst("svg_xml").getBytes("utf-8"));
            TranscoderInput input = new TranscoderInput(svgStream);
            PNGTranscoder transcoder = new PNGTranscoder();
            outStream = new ByteArrayOutputStream();
            TranscoderOutput output = new TranscoderOutput(outStream);
            transcoder.transcode(input, output);
            byte[] result = outStream.toByteArray();
            //保存图片信息
            this.repositoryService.addModelEditorSourceExtra(modelId, result);
            outStream.close();
        } catch (Exception e) {
            throw e;
        } finally {
            IoUtil.closeSilently(svgStream);
            IoUtil.closeSilently(outStream);
        }

        return true;
    }

    @Override
    public Response<Boolean> deploy(DeployReq deployReq) throws Exception {
        //获取模型信息
        Model model = repositoryService.getModel(deployReq.getModelId());
        if (model == null) {
            throw new IllegalArgumentException("无效的模型ID");
        }
        if (StringUtils.isNotBlank(deployReq.getCategory())) {
            model.setCategory(deployReq.getCategory());
        }
        if (StringUtils.isNotBlank(deployReq.getKey())) {
            model.setKey(deployReq.getKey());
        }

        //读取editorSource
        JsonNode jsonNode = objectMapper.readTree(repositoryService.getModelEditorSource(deployReq.getModelId()));
        if (jsonNode == null) {
            throw new IllegalArgumentException("流程模型不存在，请先进行流程模型设计！");
        }

        //转换editorSource为bpmnModel
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
        //获取流程名称
        Process process = bpmnModel.getProcesses().stream().findFirst().orElse(null);
        if (process == null) {
            throw new IllegalArgumentException("未查询到设计流程图,请先进行流程设计!");
        }

        process.setId(model.getKey());
        process.setName(model.getName());
        ObjectNode jsonNodes = (ObjectNode) this.objectMapper.readTree(model.getMetaInfo());
        process.setDocumentation(jsonNodes.get("description").textValue());

        //转换bpmnModel为可部署的xml形式
        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
        Deployment deployment = repositoryService.createDeployment()
                //enableDuplicateFiltering:过滤重复部署，如果部署的资源内容和资源名称一致的话，就不会再部署
                .enableDuplicateFiltering()
                .name(process.getName())
                .category(model.getCategory())
                .key(model.getKey())
                .addString(model.getKey() + ".bpmn20.xml", new String(bpmnBytes, "utf-8"))
                .deploy();

        //更新 model 的 deploymentId，进行关联
        model.setDeploymentId(deployment.getId());
        repositoryService.saveModel(model);

        //设置流程定义权限,控制发起流程实例的操作者
        ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
        deployReq.getCandidateUsers().forEach(candidateUser -> repositoryService.addCandidateStarterUser(def.getId(), candidateUser));

        return Response.success("操作成功");
    }

    @Override
    public Response<Boolean> deleteByDeployId(String deployId, boolean cascade) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        if (processDefinition == null) {
            return Response.error("找不到部署ID为:" + deployId + "的记录");
        }
        repositoryService.deleteDeployment(deployId, cascade);
        return Response.success(true, "删除成功");
    }

    @Override
    @Transactional
    public Response<Boolean> deleteByModelId(String modelId, boolean cascade) {
        Model model = repositoryService.getModel(modelId);
        if (Objects.isNull(model)) {
            return Response.error("未发现模型设计信息");
        }
        if (!StringUtils.isEmpty(model.getDeploymentId())) {
            //删除部署表
            deleteByDeployId(model.getDeploymentId(), cascade);
        }
        //删除模型表
        repositoryService.deleteModel(model.getId());

        return Response.success(true, "删除成功");
    }

    @Override
    public Response<Page<ProcessDefinitionVO, QueryProcessDefinitionListReq>> queryProcessDefinitions(QueryProcessDefinitionListReq req) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion();
        if (!Objects.isNull(req.getSuspended())) {
            processDefinitionQuery = processDefinitionQuery.suspended();
        }
        if (StringUtils.isNotBlank(req.getStartAbleByUser())) {
            processDefinitionQuery = processDefinitionQuery.startableByUser(req.getStartAbleByUser());
        }
        if (StringUtils.isNotBlank(req.getName())) {
            processDefinitionQuery = processDefinitionQuery.processDefinitionNameLike(req.getName());
        }

        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage((req.getPageIndex() - 1) * req.getPageSize(), req.getPageSize());
        if (CollectionUtils.isEmpty(processDefinitionList)) {
            return Response.success(Page.builder(new ArrayList<>(), req), "查无数据");
        }

        List<ProcessDefinitionVO> result = new ArrayList<>(processDefinitionList.size());
        processDefinitionList.forEach(processDefinition -> {
            ProcessDefinitionVO processDefinitionVO = new ProcessDefinitionVO();
            BeanUtils.copyProperties(processDefinition, processDefinitionVO);
            result.add(processDefinitionVO);
        });

        return Response.success(Page.builder(result, processDefinitionQuery.count(), req), "查询成功");
    }

    private ActBizModelReq buildActBizModelReq(String modelId, MultiValueMap<String, String> values) throws Exception {
        ActBizModelReq model = new ActBizModelReq();
        model.setModelId(modelId);
        model.setName(values.getFirst("name"));
        model.setDescription(values.getFirst("description"));

        if (StringUtils.isNotBlank(values.getFirst("category"))) {
            model.setCategory(values.getFirst("category"));
        }
        if (StringUtils.isNotBlank(values.getFirst("key"))) {
            model.setKey(values.getFirst("key"));
        }

        return model;
    }

    /**
     * 创建model json 信息
     */
    private void createSaveDefaultObjectNode(Model modelData) throws Exception {
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);

        this.repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
    }

    /**
     * 保存model信息
     *
     * @param actModelReq 工作流model对象
     * @throws Exception
     */
    private void saveActModel(Model model, ActBizModelReq actModelReq) throws Exception {
        if (Objects.isNull(model)) {
            throw new Exception("未找到模型设计对象");
        }
        ObjectNode modelJson;
        if (Objects.isNull(model.getMetaInfo())) {
            modelJson = objectMapper.createObjectNode();
        } else {
            modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            if (modelJson == null) {
                modelJson = objectMapper.createObjectNode();
            }
        }
        if (StringUtils.isNotBlank(model.getKey())) {
            model.setVersion(Integer.parseInt(
                    String.valueOf(repositoryService.createModelQuery().modelKey(model.getKey()).count() + 1)));
        }
        modelJson.put(ModelDataJsonConstants.MODEL_NAME, actModelReq.getName());
        modelJson.put(ModelDataJsonConstants.MODEL_REVISION, model.getVersion());
        modelJson.put(ModelDataJsonConstants.MODEL_DESCRIPTION, actModelReq.getDescription());

        model.setMetaInfo(modelJson.toString());
        model.setName(actModelReq.getName());

        if (StringUtils.isNotBlank(actModelReq.getCategory())) {
            model.setCategory(actModelReq.getCategory());
        }
        if (StringUtils.isNotBlank(actModelReq.getKey())) {
            model.setKey(actModelReq.getKey());
        }

        this.repositoryService.saveModel(model);
    }

}
